/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.ui.dialogs;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.emf.common.util.EList;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Stereotype;

import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.ui.dialogs.ChooseSubsetPropertiesDialog.PropertyLabelProvider;

/**
 * Dialog that allows user to add/remove stereotypes to an element.
 * 
 * @author Patrick Tessier, Remi Schnekenburger
 */
public class ChooseSetStereotypeDialog extends ChooseSetAssistedDialog implements IChooseDialog {

	/**
	 * LabelProvider for stereotype completion proposal provider with qualified names.
	 */
	final private StereotypeQualifiedLabelProvider qualifiedLabelProvider = new StereotypeQualifiedLabelProvider();
	
	/**
	 * old list of applied steretoypes.
	 */ 
	final private EList oldStereotypeList;
	
	/**
	 * the uml element on which modifications occurs.
	 */
	final private Element umlElement;
	
	/**
	 * boolean taht indicates if modifications are made to the umlElement.
	 */
	private boolean hasChanged = false; 
	
	/**
	 * holds newly created stereotypes (list updated as OK button is pressed).
	 */
	private List<Stereotype> createdStereotypes = new ArrayList<Stereotype>();
	
	/**
	 * Default Constructor.
	 * 
	 * @param parentShell the parent shell
	 * @param theElement the UML element to be modified
	 */
	public ChooseSetStereotypeDialog(Shell parentShell, Element theElement){
		super(parentShell,
				"Applicable Stereotypes: ",
		"Applied Stereotypes: ");
		umlElement = theElement;
		
		labelProvider = new StereotypeLabelProvider();
		decoratedContentProposalProvider = new StereotypeContentProposalProvider();
	
		// save old list of steretoypes (used to compare when ok is pressed)
		oldStereotypeList = theElement.getAppliedStereotypes();
		
		Iterator<Stereotype> stereotypes = oldStereotypeList.iterator();
		while (stereotypes.hasNext()){
			selectedElementList.addElement(stereotypes.next());
		}
		
		stereotypes = theElement.getApplicableStereotypes().iterator();
		while (stereotypes.hasNext()){
			Stereotype current = (Stereotype)stereotypes.next();
			if(!selectedElementList.contains(current)) {
				possibleElementList.addElement(current);
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.ui.dialogs.ChooseSetAssistedDialog#createDialogArea(org.eclipse.swt.widgets.Composite)
	 */
	/**
	 * 
	 * 
	 * @param parent 
	 * 
	 * @return 
	 */
	protected Control createDialogArea(Composite parent) {
		Control composite = super.createDialogArea(parent);
		
		// Add 2 columns to the table area
		// possibleElementsTable.setLinesVisible(true);
		possibleElementsTable.setHeaderVisible(true);

		// 1st column with image/checkboxes - NOTE: The SWT.CENTER has no effect!!
		TableColumn column = new TableColumn(possibleElementsTable, SWT.CENTER, 0);		
		column.setText("Stereotype");
		column.setWidth(150);
		column.addSelectionListener(new SelectionAdapter() {
	       	
			public void widgetSelected(SelectionEvent e) {
				possibleElementsTableViewer.setSorter(new AlphabeticalViewerSorter(0));
			}
		});
		
		// 2nd column with task Description
		column = new TableColumn(possibleElementsTable, SWT.LEFT, 1);
		column.setText("Information");
		column.setWidth(165);
		// Add listener to column so tasks are sorted by description when clicked 
		column.addSelectionListener(new SelectionAdapter() {
       	
			public void widgetSelected(SelectionEvent e) {
				possibleElementsTableViewer.setSorter(new AlphabeticalViewerSorter(1));
			}
		});

		// set sorter to the possible element table viewer 
		possibleElementsTableViewer.setSorter(new AlphabeticalViewerSorter(0));
		
		return composite;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.ui.dialogs.ChooseSetAssistedDialog#runAddElement(java.lang.String)
	 */
	/**
	 * 
	 * 
	 * @param name 
	 */
	protected void runAddElement(String name) {
		// find the stereotype in the list
		Stereotype stereotype =null;
		Iterator<Stereotype> it = possibleElementList.getElements().iterator();
		while (it.hasNext()) {
			Stereotype element = (Stereotype) it.next();
			if( name.equalsIgnoreCase(element.getName()) || name.equalsIgnoreCase(element.getQualifiedName())) {
				stereotype = element;
			}
		}
		if(stereotype != null) {
			runActionAdd(stereotype);
		}
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.core.ui.dialogs.ChooseSetAssistedDialog#isSelectableElement(java.lang.String)
	 */
	/**
	 * 
	 * 
	 * @param text 
	 * 
	 * @return 
	 */
	protected boolean isSelectableElement(String text) {
		// iterate through all possibilities and return true if text corresponds
		Iterator<Stereotype> it = possibleElementList.getElements().iterator();
		while (it.hasNext()) {
			Stereotype element = (Stereotype) it.next();
			if( text.equalsIgnoreCase(element.getName()) || text.equalsIgnoreCase(element.getQualifiedName())) {
				return true;
			}
		}
		return false;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.Dialog#okPressed()
	 */
	/**
	 * 
	 */
	protected void okPressed() {
		int modifCount = 0;
		
		// apply new sterotypes (diff old and new List)
		modifCount = createStereotypes();
		
		// unapply destroyed Stereotypes
		modifCount += destroyStereotypes();
		
		// order the list of applied Stereotypes
		modifCount += orderStereotypes();
		
		// element has changed if there is a modification or more
		hasChanged = (modifCount > 0);
		
		super.okPressed();
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	private int createStereotypes() {
		int modifCount=0;	// count number of creations (to force refresh as dialog is closed)
		// for all element of the new List that is not in the old list, apply the stereotype to the element
		Iterator newElements = selectedElementList.getElements().iterator();
		while(newElements.hasNext()) {
			Stereotype stereotype = (Stereotype) newElements.next();
			if(!oldStereotypeList.contains(stereotype)) {
				umlElement.applyStereotype(stereotype);
				createdStereotypes.add(stereotype);
				modifCount++;
			}
		}
		return modifCount;
	}
	
	/**
	 * 
	 * 
	 * @return 
	 */
	private int destroyStereotypes() {
		int modifCount=0;	// count number of destructions (to force refresh as dialog is closed)
		
		// for all element of the old List that is not in the new list, unapply the stereotype to the element
		Iterator oldElements = oldStereotypeList.iterator();
		while(oldElements.hasNext()) {
			Stereotype stereotype = (Stereotype) oldElements.next();
			if(!selectedElementList.getElements().contains(stereotype)) {
				umlElement.unapplyStereotype(stereotype);
				modifCount++;
			}
		}
		return modifCount;
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	private int orderStereotypes() {
		int modifCount=0;	// count number of destructions (to force refresh as dialog is closed)
		
		EList appliedStereotypes = umlElement.getAppliedStereotypes();
		
		// for all element of the old List that is not in the new list, unapply the stereotype to the element
		Iterator it = appliedStereotypes.iterator();
		while(it.hasNext()) {
			// position of the stereotype in the new list
			Stereotype stereotype = (Stereotype) it.next();
			int newIndex = selectedElementList.getElements().indexOf(stereotype);
			if(appliedStereotypes.indexOf(stereotype) != newIndex) {
				// stereotype should be moved
				// does not work... appliedStereotypes.move(newIndex, stereotype);
				modifCount++;
			}
		}
		return modifCount;
	}


	/**
	 * Content Proposal provider for stereotypes dialog. Propose the simple
	 * name of the stereotype and its qualified name.
	 * 
	 * @author Remi Schnekenburger
	 */
	public class StereotypeContentProposalProvider extends DecoratedContentProposalProvider {

		/* (non-Javadoc)
		 * @see com.cea.papyrus.core.ui.dialogs.ChooseSetAssistedDialog.DecoratedContentProposalProvider#getProposals(java.lang.String, int)
		 */
		/**
		 * 
		 * 
		 * @param contents 
		 * @param position 
		 * 
		 * @return 
		 */
		public DecoratedContentProposal[] getProposals(String contents, int position) {
			ArrayList<DecoratedContentProposal> proposals = new ArrayList<DecoratedContentProposal>();

			if(possibleElementList != null) {
				Iterator it = possibleElementList.getElements().iterator();
				while(it.hasNext()) {
					final Stereotype stereotype = (Stereotype)it.next();
					final String simpleName = stereotype.getName();
					final String qualifiedName = stereotype.getQualifiedName();

					if( position < simpleName.length() && contents.substring(0, position).equalsIgnoreCase(simpleName.substring(0, position))) {
						proposals.add(new DecoratedContentProposal(stereotype, labelProvider));
					}

					if( position < qualifiedName.length() && contents.substring(0, position).equalsIgnoreCase(qualifiedName.substring(0, position))) {
						proposals.add(new DecoratedContentProposal(stereotype, qualifiedLabelProvider));
					}
				}
			}
			
			Collections.sort(proposals);
			return proposals.toArray(new DecoratedContentProposal[proposals.size()]);
		}
	}

	/**
	 * Label provider for stereotypes.
	 * 
	 * @author Remi Schnekenburger
	 */
	protected class StereotypeLabelProvider extends LabelProvider implements ITableLabelProvider {

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.LabelProvider#getImage(java.lang.Object)
		 */
		/**
		 * 
		 * 
		 * @param element 
		 * 
		 * @return 
		 */
		@Override
		public Image getImage(Object element) {
			Image image = null;
			if((element instanceof Stereotype)) {
				image = PapyrusPlugin.getIconForStereotype((Stereotype)element);
			}
			// return the image if not null or the default stereotype image if stereotype has no image
			return (image != null) ? image : PapyrusPlugin.UML_LABEL_PROVIDER.getImage(Stereotype.class, null) ;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
		 */
		/**
		 * 
		 * 
		 * @param stereotype 
		 * 
		 * @return 
		 */
		@Override
		public String getText(Object stereotype) {
			if(!(stereotype instanceof Stereotype)) {
				return "not a stereotype";
			} else {
				return ((Stereotype)stereotype).getName();
			}
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int)
		 */
		/**
		 * 
		 * 
		 * @param element 
		 * @param columnIndex 
		 * 
		 * @return 
		 */
		public Image getColumnImage(Object element, int columnIndex) {
			if(columnIndex == 0) {
				return getImage(element);
			}
			return null;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int)
		 */
		/**
		 * 
		 * 
		 * @param element 
		 * @param columnIndex 
		 * 
		 * @return 
		 */
		public String getColumnText(Object element, int columnIndex) {
			if(columnIndex == 0) {
				return getText(element);
			} else if(columnIndex == 1) {
				return ((Stereotype)element).getQualifiedName();
			}
			return "not the right column index";
		}
	}

	/**
	 * Label provider for stereotypes that modifies {@link PropertyLabelProvider#getText(Object)}.
	 * It returns the qualified name of the Stereotype instead of the simple name.
	 * 
	 * @author Remi Schnekenburger
	 */
	protected class StereotypeQualifiedLabelProvider extends StereotypeLabelProvider {
		
		/* (non-Javadoc)
		 * @see com.cea.papyrus.core.ui.dialogs.ChooseSetStereotypeDialog.StereotypeLabelProvider#getText(java.lang.Object)
		 */
		/**
		 * 
		 * 
		 * @param stereotype 
		 * 
		 * @return 
		 */
		public String getText(Object stereotype) {
			if(!(stereotype instanceof Stereotype)) {
				return "not a stereotype";
			} else {
				return ((Stereotype)stereotype).getQualifiedName();
			}
		}
	}

	/**
	 * returns <code>true</code> if uml element has changed.
	 * 
	 * @return <code>true</code> if uml element has changed
	 */
	public boolean hasElementChanged() {
		return hasChanged;
	}

	/**
	 * Returns the list of newly created stereotypes.
	 * 
	 * @return the list of newly created stereotypes
	 */
	public List<Stereotype> getCreatedStereotypes() {
		return createdStereotypes;
	}
	
}
